package com.csun.cmny.provider.security;

import com.csun.cmny.core.utils.RequestUtil;
import com.csun.cmny.provider.model.domain.UacGroupIpUser;
import com.csun.cmny.provider.model.vo.ipauth.IpAddrVo;
import com.csun.cmny.provider.model.vo.ipauth.IpSegmentVo;
import com.csun.cmny.provider.model.vo.ipauth.IpUserVo;
import com.csun.cmny.provider.resolver.CmnyMessageSource;
import com.csun.cmny.provider.service.UacGroupIpAddrService;
import com.csun.cmny.provider.service.UacGroupIpSegmentService;
import com.csun.cmny.provider.service.UacGroupIpUserService;
import com.csun.cmny.provider.service.UacUserService;
import com.csun.cmny.security.core.SecurityUser;
import com.csun.cmny.util.wrapper.WrapMapper;
import com.csun.cmny.util.wrapper.Wrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.bouncycastle.util.IPAddress;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * @Auther: chisj chisj@foxmal.com
 * @Date: 2018-11-21 15:26
 * @Description: 认证成功处理器.
 */
@Component("authenticationSuccessHandler")
@Slf4j
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Resource
    private CmnyMessageSource cmnyMessageSource;
    @Resource
    private ObjectMapper objectMapper;
    @Resource
    private ClientDetailsService clientDetailsService;
    @Resource
    private UacUserService uacUserService;
    @Resource
    private AuthorizationServerTokenServices authorizationServerTokenServices;
    @Resource
    private UacGroupIpUserService uacGroupIpUserService;
    @Resource
    private UacGroupIpAddrService uacGroupIpAddrService;
    @Resource
    private UacGroupIpSegmentService uacGroupIpSegmentService;

    private static final String BEARER_TOKEN_TYPE = "Basic ";

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {

        logger.info("登录成功");

        String header = request.getHeader(HttpHeaders.AUTHORIZATION);
        log.info("header {}", header);

        if (header == null || !header.startsWith(BEARER_TOKEN_TYPE)) {
            throw new UnapprovedClientAuthenticationException("请求头中无client信息");
        }

        String[] tokens = RequestUtil.extractAndDecodeHeader(header);
        assert tokens.length == 2;

        String clientId = tokens[0];
        String clientSecret = tokens[1];

        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);

        if (clientDetails == null) {
            throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在:" + clientId);
        } else if (!this.passwordEncoder.matches(clientSecret, clientDetails.getClientSecret())) {
            throw new UnapprovedClientAuthenticationException("clientSecret不匹配:" + clientId);
        }

        TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "custom");

        OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);

        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);

        OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
        SecurityUser principal = (SecurityUser) authentication.getPrincipal();


        /**
         * @author  chisj
         * @date    2020-09-30
         * @desc    新增IP地址白名单限制
         */
        // 1.根据userId获取白名单IP地址列表
        Long groupId = principal.getGroupId();
        Long userId = principal.getUserId();


        // 2.比对白名单IP
        String remoteAddr = RequestUtil.getRemoteAddr(request);

        if (!checkIpSet(userId, groupId, remoteAddr)) {
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write((objectMapper.writeValueAsString(WrapMapper.wrap(Wrapper.ERROR_CODE,
                    cmnyMessageSource.getMessage("ip.login.limit", null), null))));

            return ;
        }

        uacUserService.handlerLoginData(token, principal, request);

        log.info("用户【 {} 】记录登录日志", principal.getUsername());

        response.setContentType("application/json;charset=UTF-8");
        // response.getWriter().write((objectMapper.writeValueAsString(WrapMapper.ok(token))));

        response.getWriter().write((objectMapper.writeValueAsString(WrapMapper.wrap(Wrapper.SUCCESS_CODE,
                cmnyMessageSource.getMessage("operation.success", null), token))));
    }

    private boolean checkIpSet(Long userId, Long groupId, String ipAddr) {

        boolean flag = true;
        // 判断是否限制用户
        List<IpUserVo> ipUserVoList = uacGroupIpUserService.selectByGroupId(groupId);
        for (IpUserVo v : ipUserVoList) {
            if (v.getUserId().equals(userId)) {
                return true;
            }
            flag = false;
        }

        // 判断是否限制IP
        List<IpAddrVo> ipAddrVoList = uacGroupIpAddrService.selectByGroupId(groupId);
        for (IpAddrVo v : ipAddrVoList) {
            if (v.getIp().equals(ipAddr)) {
                return true;
            }
            flag = false;
        }

        // 判断是否限制IP段
        List<IpSegmentVo> ipSegmentVoList = uacGroupIpSegmentService.selectByGroupId(groupId);
        for (IpSegmentVo v : ipSegmentVoList) {
            if (ipIsInRange(ipAddr, v.getIpSegment())) {
                return true;
            }
            flag = false;
        }

        return flag;
    }

    /**
     * 判断IP是否在指定IP段内，方法一（推荐）
     * ipRange IP段（以'-'分隔）
     *
     * @param ipRange
     * @param ip
     * @return boolean
     */
    public static boolean ipIsInRange(String ip, String ipRange) {
        if (ipRange == null)
            throw new NullPointerException("IP段不能为空！");
        if (ip == null)
            throw new NullPointerException("IP不能为空！");
        ipRange = ipRange.trim();
        ip = ip.trim();
        final String REGX_IP = "((25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)";
        final String REGX_IPB = REGX_IP + "\\-" + REGX_IP;
        if (!ipRange.matches(REGX_IPB) || !ip.matches(REGX_IP))
            return false;
        int idx = ipRange.indexOf('-');
        String[] sips = ipRange.substring(0, idx).split("\\.");
        String[] sipe = ipRange.substring(idx + 1).split("\\.");
        String[] sipt = ip.split("\\.");
        long ips = 0L, ipe = 0L, ipt = 0L;
        for (int i = 0; i < 4; ++i) {
            ips = ips << 8 | Integer.parseInt(sips[i]);
            ipe = ipe << 8 | Integer.parseInt(sipe[i]);
            ipt = ipt << 8 | Integer.parseInt(sipt[i]);
        }
        if (ips > ipe) {
            long t = ips;
            ips = ipe;
            ipe = t;
        }
        return ips <= ipt && ipt <= ipe;
    }

    /*
     * 判断IP是否在指定IP段内，方法二
     * ipRange IP段（以'-'分隔）
     *
     * @param ipRange
     * @param ip
     * @return boolean
     */
    public static boolean ipInRange(String ip, String ipRange) {
        int idx = ipRange.indexOf('-');
        String beginIP = ipRange.substring(0, idx);
        String endIP = ipRange.substring(idx + 1);
        return getIp2long(beginIP) <= getIp2long(ip)
                && getIp2long(ip) <= getIp2long(endIP);
    }

    public static long getIp2long(String ip) {
        String[] ips = ip.split("\\.");
        long ip2long = 0L;
        for (int i = 0; i < 4; ++i) {
            ip2long = ip2long << 8 | Integer.parseInt(ips[i]);
        }
        return ip2long;

    }

}
